<template>
  <div class="inspector-coordinations">
    <div id="coordination-input-wrap" class="coordination-input-wrap">
      <div id="coordination-input-title" class="coordination-input-title">
        添加交互
      </div>
      <div
        id="coordination-input"
        contenteditable="true"
        class="contenteditable-input"
        @focus="changeCoorInputState(true)"
        @keydown.enter="executeCoordination"
        @input="contenteditableInput()"
        @keydown.down="changeCurSuggestion(1)"
        @keydown.up="changeCurSuggestion(-1)"
      ></div>
      <div v-show="isSuggestionShow" class="select-panel">
        <div
          v-for="(pattern, i) in matchSequence"
          :key="'pattern' + i"
          :class="i === curSuggestion ? 'selected-item' : 'select-item'"
          @click.stop="selectSequence(pattern.id)"
        >
          {{ `${pattern.id}${pattern.type ? '(' + pattern.type + ')' : ''}` }}
        </div>
      </div>
    </div>
    <div class="inter-wrap">
      <a-icon type="question-circle" class="inter-help" @click="toHelp" />
    </div>
    <div class="tips">
      eg: select items in chart1 then select items in chart2
    </div>
    <a-collapse
      v-model="activeKey"
      :bordered="false"
      expand-icon-position="left"
      class="coordination-list-box"
    >
      <a-collapse-panel
        v-for="(item, index) in coordinations"
        :key="'交互' + index"
        :header="'交互' + (index + 1)"
      >
        <span class="coor-context-span">{{ item.context }}</span>
        <a-icon-font
          slot="extra"
          class="iconfont"
          :style="{ fontSize: '15px', marginRight: '5px', fontWeight: 'bold' }"
          type="iconicon-beifen6"
          @click.stop="editCoordination(item.id)"
        />
        <a-icon-font
          slot="extra"
          class="iconfont"
          style="font-size: 15px; margin-right: 5px;"
          type="iconicon-beifen5"
          @click.stop="deleteCoordination(item.id)"
        />
      </a-collapse-panel>
    </a-collapse>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import VisualizationStore, {
  ICoordination,
} from '@/store/modules/visualization'

/* eslint-disable unicorn/prefer-string-slice */
/* eslint-disable no-plusplus */
/* eslint-disable @typescript-eslint/no-unused-expressions */
/* eslint-disable no-restricted-syntax */
/* eslint-disable no-shadow */
/* eslint-disable no-continue */
@Component
export default class InteractionManager extends Vue {
  private activeKey = []
  private visualizationsPatternSequence = [
    'scatterplot',
    'linechart',
    'barchart',
    'piechart',
    'geomap',
  ]
  private visualizationsMatchSequence = []
  private isVisualzationShow: number = -1
  private interaction = []
  private isCoordinationInputShow: boolean = false
  private coordinationText: string = ''
  private isCoordinationLoading: boolean = false
  private currentCoorId: string | null = null
  private isSuggestionShow: boolean = true
  private matchSequence = []
  private curSuggestion: number | null = null
  private shouldReduce: boolean = true
  private keywords = [
    {
      id: 'any',
      type: 'pronoun',
    },
    {
      id: 'others',
      type: 'pronoun',
    },
    {
      id: 'in',
      type: 'preposition',
    },
    {
      id: 'with',
      type: 'preposition',
    },
    {
      id: 'then',
      type: 'conjunction',
    },
    {
      id: 'and',
      type: 'conjunction',
    },
    {
      id: 'select',
      type: 'verb',
    },
    {
      id: 'filter',
      type: 'verb',
    },
    {
      id: 'navigate',
      type: 'verb',
    },
    {
      id: 'encode',
      type: 'verb',
    },
    {
      id: 'reconfigure',
      type: 'verb',
    },
    {
      id: 'set',
      type: 'verb',
    },
    {
      id: 'append',
      type: 'verb',
    },
  ]

  public get dataStore() {
    return VisualizationStore
  }

  public get coordinations() {
    const currentChartId = VisualizationStore.currentGridItem?.i
    return VisualizationStore.coordinations.filter(
      (item) => item.sourceChartId === currentChartId
    )
  }

  public mounted() {
    document.addEventListener('click', this.onDocumentClick)
  }

  public beforeDestroy() {
    document.removeEventListener('click', this.onDocumentClick)
  }

  public onDocumentClick() {
    if ((document.activeElement as Element).id === 'coordination-input') {
      return
    }
    this.shouldReduce = true
    const element = document.querySelector(
      '#coordination-input-wrap'
    ) as HTMLElement
    const title = document.querySelector(
      '#coordination-input-title'
    ) as HTMLElement
    this.isSuggestionShow = false
    element.style.width = '230px'
    title.style.backgroundColor = 'transparent'
    title.style.color = 'rgba(51, 51, 51, 1)'
    title.style.marginLeft = '-20px'
    this.isCoordinationInputShow = false
  }

  public toHelp() {
    const url = 'https://nebula-vis.github.io/docs/coordinations' as string
    ;(window.open(url, '_blank') as Window).location
  }

  public addCoordination() {
    this.currentCoorId = null
    this.coordinationText = ''
    this.isCoordinationInputShow = true
    this.isCoordinationLoading = false
    setTimeout(() => {
      const element = document.querySelector(
        '#coordination-input'
      ) as HTMLInputElement
      element.focus()
      const paras = document.querySelectorAll('.cover-div')
      for (let i = paras.length - 1; i >= 0; i--) {
        if (paras[i] !== null) {
          paras[i].remove()
        }
      }
    }, 200)
  }

  public deleteCoordination(id: string) {
    VisualizationStore.deleteCoordination(id)
    this.$message.info('删除成功')
  }

  public editCoordination(id: string) {
    const coordination: ICoordination = VisualizationStore.coordinations.filter(
      (item) => item.id === id
    )[0]
    this.currentCoorId = coordination.id
    this.coordinationText = coordination.context
    this.isCoordinationInputShow = true
    const element = document.querySelector(
      '#coordination-input'
    ) as HTMLInputElement
    element.innerHTML = coordination.context
    this.contenteditableInput()
    this.setEndOfContenteditable(element)
  }

  public changeCoorInputState(tag: string) {
    const element = document.querySelector(
      '#coordination-input-wrap'
    ) as HTMLElement
    const title = document.querySelector(
      '#coordination-input-title'
    ) as HTMLElement
    if (tag) {
      this.isSuggestionShow = true
      element.style.width = '495px'
      title.style.backgroundColor = '#6475DF'
      title.style.color = 'white'
      title.style.marginLeft = '0px'
      this.isCoordinationInputShow = true
      this.contenteditableInput()
    }
  }

  public selectSequence(pattern: string) {
    const targetElement = document.querySelector(
      '#coordination-input'
    ) as HTMLDivElement
    let contextText = targetElement.textContent as string
    const inputArray = contextText.split(/\s/)
    inputArray[inputArray.length - 1] = pattern
    contextText = inputArray.join(' ')
    targetElement.innerHTML = `${contextText} `
    this.curSuggestion = null
    this.contenteditableInput()
    this.setEndOfContenteditable(targetElement)
  }

  public async executeCoordination() {
    const targetElement = document.querySelector(
      '#coordination-input'
    ) as HTMLDivElement
    if (this.curSuggestion !== null && this.matchSequence.length > 0) {
      const contextText = targetElement.textContent as string
      const inputArray = contextText.split(/\s/)
      inputArray[inputArray.length - 1] = this.matchSequence[
        this.curSuggestion
        // @ts-ignore
      ].id

      targetElement.innerHTML = `${inputArray.join(' ')} `
      this.curSuggestion = null
      this.contenteditableInput()
      setTimeout(() => {
        this.setEndOfContenteditable(targetElement)
      }, 0)
      return
    }
    if (this.coordinationText.length === 0) {
      this.$message.warning('输入不能为空')
      this.currentCoorId = null
      return
    }
    this.isCoordinationLoading = true
    // const element = document.querySelector('#coordination-input')
    // element.disabled = true
    const isCoorGrammarRight = true
    if (isCoorGrammarRight) {
      if (this.currentCoorId === null) {
        VisualizationStore.addCoordination(this.coordinationText)
        this.$message.info('添加成功')
      } else {
        VisualizationStore.updateCoordination({
          id: this.currentCoorId,
          sourceChartId: VisualizationStore.currentGridItem?.i as string,
          context: this.coordinationText,
        })
        this.$message.info('更改成功')
        this.currentCoorId = null
      }

      targetElement.innerHTML = ''
      this.coordinationText = ''
      setTimeout(() => {
        this.isCoordinationLoading = false
      }, 2000)
    } else {
      this.$message.error('语法有误，请重新编辑')
      setTimeout(() => {
        this.isCoordinationLoading = false
      }, 2000)
    }
  }

  public changeCurSuggestion(index: number) {
    // @ts-ignore
    this.curSuggestion += index
    if (this.curSuggestion === -1) {
      this.curSuggestion = this.matchSequence.length - 1
    }
    if (this.curSuggestion === this.matchSequence.length) {
      this.curSuggestion = 0
    }
  }

  public contenteditableInput() {
    const targetElement: HTMLDivElement = document.querySelector(
      '#coordination-input'
    ) as HTMLDivElement
    let text: string = targetElement.innerHTML
      .replace(/<[^>]+>/g, '')
      .replace(/\s+/g, ' ')
    const coordinationText = text.replace(/&nbsp;/g, ' ').replace(/\s+/g, ' ')
    this.coordinationText = coordinationText
    this.matchSequence.splice(0)
    const subList: any[] = []
    /** graph暂时不支持交互，需要从列表里面先排除掉 */
    for (const item of VisualizationStore.gridList) {
      if (!subList.includes((item as any).i)) {
        // const chartType = Array.isArray((item as any).chartType) ? (item as any).chartType.slice(-1)[0] : (item as any).chartType
        subList.push({
          id: (item as any).i,
          type: '',
        })
      }
    }
    subList.push(...this.keywords)
    subList.forEach((item: any) => {
      const exp = new RegExp(`\\b(${item.id})\\b`, 'gi')
      text = text.replace(exp, function (m) {
        return `<span class="keywords ${item.type}">${m}</span>`
      })
    })
    const { anchorIndex, focusIndex } = this.calculateSelection(
      this.getTextSegments(targetElement)
    )
    targetElement.innerHTML = text
    this.restoreSelection(targetElement, anchorIndex, focusIndex)

    const inputList = coordinationText.split(' ')
    if (inputList.length < 2) {
      return
    }
    const ass = inputList[inputList.length - 2]
    if (ass !== 'in') {
      return
    }
    const tail = inputList[inputList.length - 1]
    subList.forEach((item: any) => {
      const pattern = item.id
      const { type } = item
      const visIndex = pattern.toLowerCase().indexOf(tail.toLowerCase())
      // @ts-ignore
      if (visIndex !== -1 && !this.matchSequence.includes(item)) {
        Vue.set(this.matchSequence, this.matchSequence.length, item)
      }
      const typeIndex = type.toLowerCase().indexOf(tail.toLowerCase())
      // @ts-ignore
      if (typeIndex !== -1 && !this.matchSequence.includes(item)) {
        Vue.set(this.matchSequence, this.matchSequence.length, item)
      }
    })
    if (this.matchSequence.length !== 0) {
      this.curSuggestion = 0
    }
  }

  // 将光标移至末尾
  public setEndOfContenteditable(element: HTMLDivElement) {
    if (document.createRange) {
      // Firefox, Chrome, Opera, Safari, IE 9+
      const selection: Selection = window.getSelection() as Selection // get the selection object (allows you to change selection)
      const range = document.createRange() // Create a range (a range is a like the selection but invisible)
      range.selectNodeContents(element) // Select the entire contents of the element with the range
      range.collapse(false) // collapse the range to the end point. false means collapse to end rather than the start
      selection.removeAllRanges() // remove any selections already made
      selection.addRange(range) // make the range you have just created the visible selection
    }
  }

  // 根据文字内容计算当前光标偏移量
  public calculateSelection(textSegments: any[]) {
    const sel = window.getSelection() as Selection
    let anchorIndex = 0
    let focusIndex = 0
    let currentIndex = 0
    textSegments.forEach(({ text, node }) => {
      if (node === sel.anchorNode) {
        anchorIndex = currentIndex + sel.anchorOffset
      }
      if (node === sel.focusNode) {
        focusIndex = currentIndex + sel.focusOffset
      }
      currentIndex += text.length
    })

    return { anchorIndex, focusIndex }
  }

  // 获取文字内容
  public getTextSegments(element: Node) {
    const textSegments: any = []
    /* eslint-disable unicorn/prefer-spread */
    Array.from(element.childNodes).forEach((node) => {
      switch (node.nodeType) {
        case Node.TEXT_NODE:
          textSegments.push({ text: node.nodeValue, node })
          break

        case Node.ELEMENT_NODE:
          textSegments.splice(
            textSegments.length,
            0,
            ...this.getTextSegments(node)
          )
          break

        default:
          throw new Error(`Unexpected node type: ${node.nodeType}`)
      }
    })
    return textSegments
  }

  // 设置鼠标偏移量
  public restoreSelection(
    element: HTMLDivElement,
    absoluteAnchorIndex: number,
    absoluteFocusIndex: number
  ) {
    const selection = window.getSelection() as Selection
    const textSegments = this.getTextSegments(element)
    let anchorNode = element
    let anchorIndex = 0
    let focusNode = element
    let focusIndex = 0
    let currentIndex = 0
    // @ts-ignore
    textSegments.forEach(({ text, node }) => {
      const startIndexOfNode = currentIndex
      const endIndexOfNode = startIndexOfNode + text.length
      if (
        startIndexOfNode <= absoluteAnchorIndex &&
        absoluteAnchorIndex <= endIndexOfNode
      ) {
        anchorNode = node
        anchorIndex = absoluteAnchorIndex - startIndexOfNode
      }
      if (
        startIndexOfNode <= absoluteFocusIndex &&
        absoluteFocusIndex <= endIndexOfNode
      ) {
        focusNode = node
        focusIndex = absoluteFocusIndex - startIndexOfNode
      }
      currentIndex += text.length
    })

    selection.setBaseAndExtent(anchorNode, anchorIndex, focusNode, focusIndex)
  }
}
</script>

<style lang="less" scoped>
.inspector-coordinations {
  height: 100%;
  position: relative;
  text-align: left;
  width: 100%;

  .coordination-input-wrap {
    height: 72px;
    position: fixed;
    right: 20px;
    top: 60px;
    transition: all 0.3s ease;
    width: 230px;
    z-index: 100;

    .coordination-input-loading {
      color: @primary-color;
      left: -20px;
      position: absolute;
      top: 30px;
      z-index: 999;
    }

    .coordination-input-title {
      border-radius: 4px 4px 0 0;
      font-size: 14px;
      font-weight: 500;
      height: 32px;
      letter-spacing: 1px;
      line-height: 32px;
      margin-left: -20px;
      text-align: center;
      width: 106px;
      z-index: 999;
    }

    .coordination-input-context {
      caret-color: #333;
      font-size: 14px;
      font-weight: 400;
      letter-spacing: 1px;
      line-height: 20px;
      z-index: 999;
    }

    .coordination-input-button {
      cursor: pointer;
    }

    .select-panel {
      display: inline-block;
      height: 0;
      left: 0;
      position: relative;
      top: 0;
      width: 100%;
      z-index: 999;

      .select-item {
        background-color: #fff;
        cursor: pointer;
        display: flex;
        flex-flow: row nowrap;
        justify-content: flex-start;
        opacity: 1;
        padding: 2px;
        padding-left: 10px;
        padding-right: 10px;
        z-index: 999;
      }

      .selected-item {
        background-color: #eff0fe;
        cursor: pointer;
        display: flex;
        flex-flow: row nowrap;
        justify-content: flex-start;
        opacity: 1;
        padding: 2px;
        padding-left: 10px;
        padding-right: 10px;
        z-index: 999;
      }
    }
  }

  .contenteditable-input {
    background-color: #fff;
    border: 1px solid #d9d9d9;
    border-radius: 2px;
    height: 32px;
    line-height: 20px;
    min-height: 32px;
    overflow: auto;
    padding: 4px 11px;
    white-space: nowrap;

    &:focus {
      border-color: #7d8aff;
      border-right-width: 1px !important;
      box-shadow: 0 0 0 2px rgba(85, 97, 255, 0.2);
      height: auto;
      outline: 0;
    }

    /deep/ .keywords {
      background-color: #7b88f2;
      color: #fff;
      padding: 0 5px;

      &.verb {
        background-color: #f596c6;
      }

      &.preposition {
        background-color: #fbc94b;
      }

      &.pronoun {
        background-color: #65dcb8;
      }

      &.conjunction {
        background-color: #ef7d52;
      }

      &.verb {
        background-color: #f596c6;
      }
    }
  }

  .inter-wrap {
    font-size: 15px;
    height: 87px;
    position: absolute;
    right: 25px;
    top: 22px;
    z-index: 120;
  }

  .tips {
    color: #999;
    font-size: 12px;
    padding: 90px 10px 10px 20px;
  }

  .coordination-list-box {
    height: calc(100% - 136px);
    overflow-y: auto;
  }

  .coor-context-span {
    width: 100%;
    word-wrap: break-word;
  }

  .coor-add-button {
    left: 50%;
    position: absolute;
    top: 5%;
    transform: translate(-50%, -50%);
  }

  .visualizations-select-panel {
    height: 0;
    left: 0;
    position: relative;
    top: 0;
    width: 250px;
    z-index: 999;

    .select-item {
      background: #fff;
      cursor: pointer;
      display: flex;
      flex-flow: row nowrap;
      justify-content: flex-start;
      margin-top: 1px;
      opacity: 0.8;
      padding: 2px;
      z-index: 999;
    }
  }

  .button-wrap {
    display: flex;
    flex-flow: row nowrap;
    justify-content: space-around;
    margin-top: 5px;
  }

  .interactions-help {
    bottom: 10px;
    position: absolute;
    right: 45px;
  }

  .add-interaction {
    bottom: 10px;
    position: absolute;
    right: 10px;
  }
}
</style>
